﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Text;

namespace FlitBit.Parse
{
	/// <summary>
	///   Cursor used by parsing methods. Tracks offset, line, and line-position.
	/// </summary>
	public struct Cursor : IEquatable<Cursor>
	{
		/// <summary>
		///   Special invalid cursor.
		/// </summary>
		public static readonly Cursor Invalid = new Cursor(-1);

		static readonly int CHashCodeSeed = typeof(Cursor).AssemblyQualifiedName.GetHashCode();

		readonly int _column;
		readonly int _line;
		readonly int _offs;

		/// <summary>
		///   Creates a new instance initialized to the offset given.
		/// </summary>
		/// <param name="ofs"></param>
		public Cursor(int ofs)
		{
			_offs = _column = ofs;
			_line = 0;
		}

		/// <summary>
		///   Creates a new instance with initialized values for offset,
		///   line, and line-position.
		/// </summary>
		/// <param name="offs">Offset from the beginning of the parse input.</param>
		/// <param name="line">Zero based line number where the offset occurs.</param>
		/// <param name="linepos">Zero based character position within the line.</param>
		public Cursor(int offs, int line, int linepos)
		{
			_offs = offs;
			_line = line;
			_column = linepos;
		}

		/// <summary>
		///   Copy constructor.
		/// </summary>
		/// <param name="c">Copy cursor</param>
		public Cursor(Cursor c)
		{
			_offs = c._offs;
			_line = c._line;
			_column = c._column;
		}

		/// <summary>
		///   Zero based character position within the line.
		/// </summary>
		public int Column { get { return _column; } }

		/// <summary>
		///   Indicates whether the cursor is valid.
		/// </summary>
		public bool IsValid { get { return this != Invalid; } }

		/// <summary>
		///   Zero based line number where offset occurs.
		/// </summary>
		public int Line { get { return _line; } }

		/// <summary>
		///   Zero based offset from beginning of input.
		/// </summary>
		public int Offset { get { return _offs; } }

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to. </param><filterpriority>2</filterpriority>
		public override bool Equals(object obj)
		{
			return obj is Cursor
				&& Equals((Cursor) obj);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override int GetHashCode()
		{
			const int prime = 999067; // a random prime

			var result = CHashCodeSeed * prime;
			result ^= prime * _offs;
			result ^= prime * _line;
			result ^= prime * _column;
			return result;
		}

		/// <summary>
		/// Returns the fully qualified type name of this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> containing a fully qualified type name.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override string ToString()
		{
			return new StringBuilder(65).Append("{ Offset: ")
																	.Append(_offs)
																	.Append(", Line: ")
																	.Append(_line)
																	.Append(", Column: ")
																	.Append(_column)
																	.Append("}")
																	.ToString();
		}

		/// <summary>
		///   Increments the cursor.
		/// </summary>
		/// <returns>Cursor representing the incremented position</returns>
		public Cursor Increment()
		{
			return new Cursor(_offs + 1, _line, _column + 1);
		}

		/// <summary>
		///   Increments the cursor.
		/// </summary>
		/// <param name="count">indicates the number to increment the cursor by</param>
		/// <returns>Cursor representing the incremented position</returns>
		public Cursor Increment(int count)
		{
			return new Cursor(_offs + count, _line, _column + count);
		}

		/// <summary>
		///   Increments the cursor to reflect a new line. (single character newline)
		/// </summary>
		/// <returns>Cursor reflecting the new position</returns>
		public Cursor Newline()
		{
			return new Cursor(_offs + 1, _line + 1, 0);
		}

		/// <summary>
		///   Increments the cursor to reflect a new line. (multiple character newline)
		/// </summary>
		/// <param name="charCount">number of characters reflecting the new line</param>
		/// <returns>Cursor reflecting the new position</returns>
		public Cursor Newline(int charCount)
		{
			return new Cursor(_offs + charCount, _line + 1, 0);
		}

		/// <summary>
		///   Implicit + operator for incrementing a cursor by an integer value.
		/// </summary>
		/// <param name="c">cursor to be incremented</param>
		/// <param name="inc">number by which the cursor is incremented</param>
		/// <returns>an incremented cursor</returns>
		public static Cursor operator +(Cursor c, int inc)
		{
			return c.Increment(inc);
		}

		/// <summary>
		///   Implicit + operator for incrementing a cursor by an integer value.
		/// </summary>
		/// <param name="c">cursor to be incremented</param>
		/// <param name="inc">number by which the cursor is incremented</param>
		/// <returns>an incremented cursor</returns>
		public static Cursor operator +(Cursor c, Cursor inc)
		{
			return c.Increment((int) inc);
		}

		/// <summary>
		///   Equality operator; determines if two cursors are equal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator ==(Cursor lhs, Cursor rhs)
		{
			return lhs.Equals(rhs);
		}

		/// <summary>
		///   Equality operator; determines if two values are equal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator ==(Cursor lhs, int rhs)
		{
			return lhs.Offset == rhs;
		}

		/// <summary>
		///   Equality operator; determines if two values are equal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator ==(int lhs, Cursor rhs)
		{
			return lhs == rhs.Offset;
		}

		/// <summary>
		///   Implicit conversion operator to Int32
		/// </summary>
		/// <param name="c">cursor to convert</param>
		/// <returns>an integer representing the cursor position</returns>
		public static implicit operator int(Cursor c)
		{
			return c._offs;
		}

		/// <summary>
		///   Implicit conversion operator from Int32 to Cursor.
		/// </summary>
		/// <param name="offset">value to convert</param>
		/// <returns>a cursor representing the offset</returns>
		public static implicit operator Cursor(int offset)
		{
			return new Cursor(offset);
		}

		/// <summary>
		///   Implicit ++ operator for incrementing a cursor's position.
		/// </summary>
		/// <param name="c">cursor being incremented</param>
		/// <returns>the incremented cursor</returns>
		public static Cursor operator ++(Cursor c)
		{
			return c.Increment();
		}

		/// <summary>
		///   Inequality operator; determines if two values are inequal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator !=(Cursor lhs, Cursor rhs)
		{
			return !lhs.Equals(rhs);
		}

		/// <summary>
		///   Inequality operator; determines if two values are inequal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator !=(Cursor lhs, int rhs)
		{
			return lhs.Offset != rhs;
		}

		/// <summary>
		///   Inequality operator; determines if two values are inequal.
		/// </summary>
		/// <param name="lhs"></param>
		/// <param name="rhs"></param>
		/// <returns></returns>
		public static bool operator !=(int lhs, Cursor rhs)
		{
			return lhs != rhs.Offset;
		}

		#region IEquatable<Cursor> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.</param>
		public bool Equals(Cursor other)
		{
			return _offs == other._offs
				&& _line == other._line
				&& _column == other._column;
		}

		#endregion
	}
}